package org.xhy.circuitbreaker;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.xhy.circuitbreaker.alert.AlertService;
import org.xhy.circuitbreaker.config.CircuitBreakerProperties;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @gitee: https://gitee.com/XhyQAQ
 * @copyright: B站: https://space.bilibili.com/152686439
 * @Author: Xhy
 * @CreateTime: 2024-06-04 16:59
 */
@Component
public class CircuitBreakerController implements ICircuitBreakerController, ApplicationContextAware {


    private ThreadPoolExecutor threadPoolExecutor;

    private DelayQueue<MethodWrapper> delayQueue = new DelayQueue<>();

    // 熔断的方法
    private Set<Integer> methods = new HashSet<>();


    private CircuitBreakerProperties circuitBreakerProperties;

    private ApplicationContext applicationContext;

    public CircuitBreakerController(CircuitBreakerProperties circuitBreakerProperties){
        this.threadPoolExecutor = new ThreadPoolExecutor(1, 5, 60,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
        this.circuitBreakerProperties = circuitBreakerProperties;
        run();
    }

    private void run(){
        final Thread thread = new Thread(() -> {
            while (true){
                try {
                    MethodWrapper methodWrapper = delayQueue.take();
                    if (methodWrapper !=null){
                        threadPoolExecutor.execute(()->{
                            try {
                                methodWrapper.invoke();
                                final Method method = methodWrapper.getMethod();
                                methods.remove(method.hashCode());
                            } catch (Exception e) {
                                methodWrapper.incrRetry();
                                if (methodWrapper.getRetryCount() >= circuitBreakerProperties.getMaxRetry()) {
                                    // 告警
                                    final AlertService alertService = applicationContext.getBean(circuitBreakerProperties.getAlertName(), AlertService.class);
                                    alertService.alert(methodWrapper);
                                }else {
                                    methodWrapper.setExpire(new Date().getTime() + circuitBreakerProperties.getRetryInterval());
                                    delayQueue.add(methodWrapper);
                                }

                            }
                        });
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

    @Override
    public void trigger(MethodWrapper methodInvoker) {
        methodInvoker.setExpire(new Date().getTime() + circuitBreakerProperties.getRetryInterval());
        methods.add(methodInvoker.getMethod().hashCode());
        delayQueue.add(methodInvoker);
    }

    @Override
    public boolean exist(int methodHashCode) {
        return methods.contains(methodHashCode);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
